/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.common.reflect;

import org.vacoor.nothing.common.util.Primitives;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

/**
 * 方法查找工具
 *
 * @author vacoor
 */
public abstract class MethodFinder {
    protected final String name;
    protected final Class<?>[] args;

    /**
     * 获取给定方法名称中可应用给定参数的最佳方法
     */
    public static Method findMethod(Class<?> clazz, final boolean ignoreModifier, String method, Class<?>... args) throws NoSuchMethodException {
        MethodFinder finder = new MethodFinder(method, args) {
            @Override
            protected boolean isValid(Method method) {
                boolean valid = super.isValid(method);

                return valid ? true : ignoreModifier && method.getName().equals(this.name);
            }
        };
        // return finder.find(clazz.getMethods());
        return finder.find(clazz.getDeclaredMethods());
    }

    /**
     * 获取给定方法名称中可应用给定参数的最佳实例方法
     */
    public static Method findInstanceMethod(Class<?> clazz, final boolean ignoreModifier, String method, Class<?>... args) throws NoSuchMethodException {
        MethodFinder finder = new MethodFinder(method, args) {
            @Override
            protected boolean isValid(Method method) {
                boolean valid = super.isValid(method) && !Modifier.isStatic(method.getModifiers());
                return valid ? true : ignoreModifier && method.getName().equals(this.name);
            }
        };
        // return finder.find(clazz.getMethods());
        return finder.find(clazz.getDeclaredMethods());
    }

    /**
     * 获取给定方法名称中可应用给定参数的最佳静态方法
     */
    public static Method findStaticMethod(Class<?> clazz, final boolean ignoreMidifier, String method, Class<?>... args) throws NoSuchMethodException {
        MethodFinder finder = new MethodFinder(method, args) {
            @Override
            protected boolean isValid(Method method) {
                boolean valid = super.isValid(method) && Modifier.isStatic(method.getModifiers());
                return valid ? true : ignoreMidifier && method.getName().equals(this.name);
            }
        };
        // return finder.find(clazz.getMethods());
        return finder.find(clazz.getDeclaredMethods());
    }

    /* **********
     *
     * **********/

    protected MethodFinder(String name, Class<?>... args) {
        this.name = name;
        this.args = args;
    }

    protected boolean isValid(Method method) {
        return method.getName().equals(name) && Modifier.isPublic(method.getModifiers());
    }

    /**
     * 获取给定参数类型能匹配的最佳方法
     *
     * @param methods
     * @return
     * @throws NoSuchMethodException
     */
    public final Method find(Method[] methods) throws NoSuchMethodException {
        Map<Method/*method*/, Class<?>[]/*args*/> candidates = new HashMap<Method, Class<?>[]>();

        Method bestMethod = null;
        Class<?>[] bestArgs = null;
        boolean isAmbiguous = false;

        for (Method method : methods) {
            if (!isValid(method)) {
                continue;
            }

            Class[] args = method.getParameterTypes();

            // 如果参数列表长度相同
            if (args.length == this.args.length) {
                args = Primitives.wrap(args);

                // 如果当前参数适用
                if (isAssignable(args, this.args)) {
                    if (bestMethod == null) {
                        bestMethod = method;
                        bestArgs = args;
                    } else {
                        /**
                         * 这里需要对以下情况处理:
                         * <A extends Serializable> test(A a);
                         * <B extends CharSequence> test(B b);
                         * args = [String.class]
                         */
                        boolean isSubclass = isAssignable(bestArgs, args);
                        boolean isSuperclass = isAssignable(args, bestArgs);

                        if (isSuperclass && isSubclass) {
                            isSubclass = !method.isSynthetic();
                            isSuperclass = !bestMethod.isSynthetic();
                        }

                        if (isSuperclass == isSubclass) {
                            isAmbiguous = true;
                        } else if (isSubclass) {
                            isAmbiguous = false;
                            bestMethod = method;
                            bestArgs = args;
                        }
                    }
                }
            }

            // 可变参数
            if (method.isVarArgs()) {
                int len = args.length - 1;

                if (len <= this.args.length) {
                    Class<?>[] argTypes = new Class<?>[this.args.length];
                    System.arraycopy(args, 0, argTypes, 0, len);

                    if (len < this.args.length) {
                        Class<?> componentType = args[len].getComponentType();
                        if (componentType.isPrimitive()) {
                            componentType = Primitives.wrap(componentType);
                        }

                        for (int i = len; i < this.args.length; i++) {
                            argTypes[i] = componentType;
                        }
                    }

                    candidates.put(method, argTypes);
                }
            }
        }

        for (Map.Entry<Method, Class<?>[]> candidate : candidates.entrySet()) {
            Method method = candidate.getKey();
            Class<?>[] args = candidate.getValue();

            if (isAssignable(args, this.args)) {
                if (bestMethod == null) {
                    bestMethod = method;
                    bestArgs = args;
                } else {
                    boolean accuratelyType = isAssignable(bestArgs, args);
                    boolean accuratelyParams = isAssignable(args, bestArgs);

                    if (accuratelyParams && accuratelyType) {
                        accuratelyType = !method.isSynthetic();
                        accuratelyParams = !bestMethod.isSynthetic();
                    }

                    if (accuratelyParams == accuratelyType) {
                        if (bestArgs == candidates.get(bestMethod)) {
                            isAmbiguous = true;
                        }
                    } else if (accuratelyType) {
                        isAmbiguous = false;
                        bestMethod = method;
                        bestArgs = args;
                    }
                }
            }
        }

        if (isAmbiguous) {
            throw new NoSuchMethodException("Ambiguous methods are found");
        }

        if (bestMethod == null) {
            throw new NoSuchMethodException("Method is not found");
        }

        return bestMethod;
    }


    /**
     * 是否给定 superclasses 中每个元素都是 subclasses 中对应位置的父类/接口
     *
     * @param superclasses
     * @param subclasses
     * @return
     */
    private boolean isAssignable(Class<?>[] superclasses, Class<?>[] subclasses) {
        for (int i = 0; i < this.args.length; i++) {
            if (this.args[i] != null && !superclasses[i].isAssignableFrom(subclasses[i])) {
                return false;
            }
        }
        return true;
    }
}
